package jonet

import (
	"github.com/pkg/errors"
	"net"
	"sync"
)

type TcpServer struct {
	statusLocker *sync.RWMutex
	tcpAcceptor  ITcpAcceptor
	tcpListener  *net.TCPListener
}

func (s *TcpServer) Start(addr string, acceptor ITcpAcceptor) error {
	listener := s.getListener()
	if listener != nil {
		return errors.New("server started already")
	}

	if err := s.startListen(addr, acceptor); err != nil {
		return errors.Wrap(err, "failed to start tcp server")
	}

	return nil
}

func (s *TcpServer) Stop() {
	listener := s.getListener()
	if listener != nil {
		func() {
			s.statusLocker.Lock()
			defer s.statusLocker.Unlock()
			if s.tcpListener != nil {
				_ = s.tcpListener.Close()
				s.tcpListener = nil
			}
		}()
	}
}

func (s *TcpServer) startListen(addr string, acceptor ITcpAcceptor) error {
	s.statusLocker.Lock()
	defer s.statusLocker.Unlock()

	if s.tcpListener != nil {
		return errors.New("server started already")
	}

	var tcpAddr *net.TCPAddr
	if a, err := net.ResolveTCPAddr("tcp", addr); err != nil {
		return errors.Wrapf(err, "try resolve tcp addr '%s' fail", addr)
	} else {
		tcpAddr = a
	}

	if lis, err := net.ListenTCP("tcp", tcpAddr); err != nil {
		return errors.Wrapf(err, "try listen tcp addr '%s' fail", addr)
	} else if lis == nil {
		return errors.Errorf("try listen tcp addr '%s' returns nil listener", addr)
	} else {
		go s.listenProcessor(lis)
		s.tcpAcceptor = acceptor
		s.tcpListener = lis
	}

	return nil
}

func (s *TcpServer) listenProcessor(lis *net.TCPListener) {
	if lis == nil {
		return
	}

	defer func() {
		_ = lis.Close()
	}()

	for {
		conn, err := lis.AcceptTCP()
		if err != nil {
			if errors.Is(err, net.ErrClosed) {
				// socket closed
				return
			}

			if s.tcpAcceptor != nil {
				s.tcpAcceptor.OnError(err)
			}
			continue
		}

		if s.tcpAcceptor != nil {
			go s.tcpAcceptor.Accept(conn)
		}
	}
}

func (s *TcpServer) getListener() *net.TCPListener {
	s.checkStatusLocker()
	s.statusLocker.RLock()
	defer s.statusLocker.RUnlock()
	return s.tcpListener
}

func (s *TcpServer) checkStatusLocker() {
	if s.statusLocker == nil {
		s.statusLocker = &sync.RWMutex{}
	}
}
